//------------------------------------------------------------------------
//
// Model Railroading with Arduino - NmraDcc.cpp
//
// Copyright (c) 2008 - 2020 Alex Shepherd
//
// 	This library is free software; you can redistribute it and/or
// 	modify it under the terms of the GNU Lesser General Public
// 	License as published by the Free Software Foundation; either
// 	version 2.1 of the License, or (at your option) any later version.
//
// 	This library is distributed in the hope that it will be useful,
// 	but WITHOUT ANY WARRANTY; without even the implied warranty of
// 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// 	Lesser General Public License for more details.
//
// 	You should have received a copy of the GNU Lesser General Public
// 	License along with this library; if not, write to the Free Software
// 	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
//------------------------------------------------------------------------
//
// file:      NmraDcc.cpp
// author:    Alex Shepherd
// webpage:   http://mrrwa.org/
// history:   2008-03-20 Initial Version
//            2011-06-26 Migrated into Arduino library from OpenDCC codebase
//            2014 Added getAddr to NmraDcc  Geoff Bunza
//            2015-11-06 Martin Pischky (martin@pischky.de):
//                       Experimental Version to support 14 speed steps
//                       and new signature of notifyDccSpeed and notifyDccFunc
//            2015-12-16 Version without use of Timer0 by Franz-Peter Müller
//            2016-07-16 handle glitches on DCC line
//			  2016-08-20 added ESP8266 support by Sven (littleyoda)
//			  2017-01-19 added STM32F1 support by Franz-Peter
//            2017-11-29 Ken West (kgw4449@gmail.com):
//                       Minor fixes to pass NMRA Baseline Conformance Tests.
//            2018-12-17 added ESP32 support by Trusty (thierry@lapajaparis.net)
//            2019-02-17 added ESP32 specific changes by Hans Tanner
//            2020-05-15 changes to pass NMRA Tests ( always search for preamble )
//            2021-03-11 fix ESP32 bug on interrupt reinitialisation
//------------------------------------------------------------------------
//
// purpose:   Provide a simplified interface to decode NMRA DCC packets
//			  and build DCC Mobile and Stationary Decoders
//
//------------------------------------------------------------------------

#include "NmraDcc.h"
#ifdef ARDUINO_SAMD_ZERO
#include <FlashStorage_SAMD.h>
#else
#include "EEPROM.h"
#endif

// Uncomment to print DEBUG messages
// #define DEBUG_PRINT

//------------------------------------------------------------------------
// DCC Receive Routine
//
// Howto:    uses two interrupts: a rising edge in DCC polarity triggers INTx
//           in INTx handler, Timer0 CompareB with a delay of 80us is started.
//           On Timer0 CompareB Match the level of DCC is evaluated and
//           parsed.
//
//                           |<-----116us----->|
//
//           DCC 1: _________XXXXXXXXX_________XXXXXXXXX_________
//                           ^-INTx
//                           |----87us--->|
//                                        ^Timer-INT: reads zero
//
//           DCC 0: _________XXXXXXXXXXXXXXXXXX__________________
//                           ^-INTx
//                           |----------->|
//                                        ^Timer-INT: reads one
//
// new DCC Receive Routine without Timer0 ........................................................
//
// Howto:    uses only one interrupt at the rising or falling edge of the DCC signal
//           The time between two edges is measured to determine the bit value
//           Synchronising to the edge of the first part of a bit is done after recognizing the start bit
//           During synchronizing each part of a bit is detected ( Interruptmode 'change' )
//
//                           |<-----116us----->|
//           DCC 1: _________XXXXXXXXX_________XXXXXXXXX_________
//                           |<--------146us------>|
//                           ^-INTx            ^-INTx
//                           less than 146us: its a one-Bit
//
//
//                           |<-----------------232us----------->|
//           DCC 0: _________XXXXXXXXXXXXXXXXXX__________________XXXXXXXX__________
//                           |<--------146us------->|
//                           ^-INTx                              ^-INTx
//                           greater than 146us: its a zero bit
//
//
//
//

//------------------------------------------------------------------------
// if this is commented out, bit synchronisation is only done after a wrong checksum
#define SYNC_ALWAYS

// if this is commented out, Zero-Bit_Stretching is not supported
// ( Bits longer than 2* MAX ONEBIT are treated as error )
#define SUPPORT_ZERO_BIT_STRETCHING

#define MAX_ONEBITFULL  146
#define MAX_PRAEAMBEL   146
#define MAX_ONEBITHALF  82
#define MIN_ONEBITFULL  82
#define MIN_ONEBITHALF  35
#define MAX_BITDIFF     24


// Debug-Ports
//#define debug     // Testpulse for logic analyser
#ifdef debug
    #if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
        #define MODE_TP1 DDRF |= (1<<2) //pinA2
        #define SET_TP1 PORTF |= (1<<2)
        #define CLR_TP1 PORTF &= ~(1<<2)
        #define MODE_TP2 DDRF |= (1<<3) //pinA3
        #define SET_TP2 PORTF |= (1<<3)
        #define CLR_TP2 PORTF &= ~(1<<3)
        #define MODE_TP3 DDRF |= (1<<4) //pinA4
        #define SET_TP3 PORTF |= (1<<4)
        #define CLR_TP3 PORTF &= ~(1<<4)
        #define MODE_TP4 DDRF |= (1<<5) //pinA5
        #define SET_TP4 PORTF |= (1<<5)
        #define CLR_TP4 PORTF &= ~(1<<5)
    #elif defined(__AVR_ATmega32U4__)
        #define MODE_TP1 DDRF |= (1<<4) //A3
        #define SET_TP1 PORTF |= (1<<4)
        #define CLR_TP1 PORTF &= ~(1<<4)
        #define MODE_TP2 DDRF |= (1<<5) //A2
        #define SET_TP2 PORTF |= (1<<5)
        #define CLR_TP2 PORTF &= ~(1<<5)
        #define MODE_TP3
        #define SET_TP3
        #define CLR_TP3
        #define MODE_TP4
        #define SET_TP4
        #define CLR_TP4
    #elif defined(__AVR_ATmega328P__)
        #define MODE_TP1 DDRC |= (1<<1) //A1
        #define SET_TP1 PORTC |= (1<<1)
        #define CLR_TP1 PORTC &= ~(1<<1)
        #define MODE_TP2 DDRC |= (1<<2) // A2
        #define SET_TP2 PORTC |= (1<<2)
        #define CLR_TP2 PORTC &= ~(1<<2)
        #define MODE_TP3 DDRC |= (1<<3) //A3
        #define SET_TP3 PORTC |= (1<<3)
        #define CLR_TP3 PORTC &= ~(1<<3)
        #define MODE_TP4 DDRC |= (1<<4) //A4
        #define SET_TP4 PORTC |= (1<<4)
        #define CLR_TP4 PORTC &= ~(1<<4)
    #elif defined(__arm__) && (defined(__MK20DX128__) || defined(__MK20DX256__))
        // Teensys 3.x
        #define MODE_TP1 pinMode( A1,OUTPUT )   // A1= PortC, Bit0
        #define SET_TP1  GPIOC_PSOR = 0x01
        #define CLR_TP1  GPIOC_PCOR = 0x01
        #define MODE_TP2 pinMode( A2,OUTPUT )   // A2= PortB Bit0
        #define SET_TP2  GPIOB_PSOR = 0x01
        #define CLR_TP2  GPIOB_PCOR = 0x01
        #define MODE_TP3 pinMode( A3,OUTPUT )   // A3 = PortB Bit1
        #define SET_TP3  GPIOB_PSOR = 0x02
        #define CLR_TP3  GPIOB_PCOR = 0x02
        #define MODE_TP4 pinMode( A4,OUTPUT )   // A4 = PortB Bit3
        #define SET_TP4  GPIOB_PSOR = 0x08
        #define CLR_TP4  GPIOB_PCOR = 0x08
    #elif defined (__STM32F1__)
        // STM32F103...
        #define MODE_TP1 pinMode( PB12,OUTPUT )   // TP1= PB12
        #define SET_TP1  gpio_write_bit( GPIOB,12, HIGH );
        #define CLR_TP1  gpio_write_bit( GPIOB,12, LOW );
        #define MODE_TP2 pinMode( PB13,OUTPUT )   // TP2= PB13
        #define SET_TP2  gpio_write_bit( GPIOB,13, HIGH );
        #define CLR_TP2  gpio_write_bit( GPIOB,13, LOW );
        #define MODE_TP3 pinMode( PB14,OUTPUT )   // TP3 = PB14
        #define SET_TP3  gpio_write_bit( GPIOB,14, HIGH );
        #define CLR_TP3  gpio_write_bit( GPIOB,14, LOW );
        #define MODE_TP4 pinMode( PB15,OUTPUT )   // TP4 = PB15
        #define SET_TP4  gpio_write_bit( GPIOB,15, HIGH );
        #define CLR_TP4  gpio_write_bit( GPIOB,15, LOW );
    #elif defined(ESP8266)
        #define MODE_TP1 pinMode( D5,OUTPUT ) ; // GPIO 14
        #define SET_TP1  GPOS = (1 << D5);
        #define CLR_TP1  GPOC = (1 << D5);
        #define MODE_TP2 pinMode( D6,OUTPUT ) ; // GPIO 12
        #define SET_TP2  GPOS = (1 << D6);
        #define CLR_TP2  GPOC = (1 << D6);
        #define MODE_TP3 pinMode( D7,OUTPUT ) ; // GPIO 13
        #define SET_TP3  GPOS = (1 << D7);
        #define CLR_TP3  GPOC = (1 << D7);
        #define MODE_TP4 pinMode( D8,OUTPUT ) ; // GPIO 15
        #define SET_TP4  GPOS = (1 << D8);
        #define CLR_TP4  GPOC = (1 << D8);
    #elif defined(ESP32)
        #define MODE_TP1 pinMode( 33,OUTPUT ) ; // GPIO 33
        #define SET_TP1  GPOS = (1 << 33);
        #define CLR_TP1  GPOC = (1 << 33);
        #define MODE_TP2 pinMode( 25,OUTPUT ) ; // GPIO 25
        #define SET_TP2  GPOS = (1 << 25);
        #define CLR_TP2  GPOC = (1 << 25);
        #define MODE_TP3 pinMode( 26,OUTPUT ) ; // GPIO 26
        #define SET_TP3  GPOS = (1 << 26);
        #define CLR_TP3  GPOC = (1 << 26);
        #define MODE_TP4 pinMode( 27,OUTPUT ) ; // GPIO 27
        #define SET_TP4  GPOS = (1 << 27);
        #define CLR_TP4  GPOC = (1 << 27);


        //#elif defined(__AVR_ATmega128__) ||defined(__AVR_ATmega1281__)||defined(__AVR_ATmega2561__)
    #else
        #define MODE_TP1
        #define SET_TP1
        #define CLR_TP1
        #define MODE_TP2
        #define SET_TP2
        #define CLR_TP2
        #define MODE_TP3
        #define SET_TP3
        #define CLR_TP3
        #define MODE_TP4
        #define SET_TP4
        #define CLR_TP4

    #endif
#else
    #define MODE_TP1
    #define SET_TP1
    #define CLR_TP1
    #define MODE_TP2
    #define SET_TP2
    #define CLR_TP2
    #define MODE_TP3
    #define SET_TP3
    #define CLR_TP3
    #define MODE_TP4
    #define SET_TP4
    #define CLR_TP4

#endif
#ifdef DEBUG_PRINT
    #define DB_PRINT( x, ... ) { char dbgbuf[80]; sprintf_P( dbgbuf, (const char*) F( x ) , ##__VA_ARGS__ ) ; Serial.println( dbgbuf ); }
    #define DB_PRINT_( x, ... ) { char dbgbuf[80]; sprintf_P( dbgbuf, (const char*) F( x ) , ##__VA_ARGS__ ) ; Serial.print( dbgbuf ); }
#else
    #define DB_PRINT( x, ... ) ;
    #define DB_PRINT_( x, ... ) ;
#endif

#ifdef DCC_DBGVAR
    struct countOf_t countOf;
#endif

#if defined ( __STM32F1__ )
    static ExtIntTriggerMode ISREdge;
#elif defined ( ESP32 )
    static byte  ISREdge;   // Holder of the Next Edge we're looking for: RISING or FALLING
    static byte  ISRWatch;  // Interrupt Handler Edge Filter
#elif defined( ESP8266 )
    static byte  ISREdge;   // Holder of the Next Edge we're looking for: RISING or FALLING
#elif defined ( ARDUINO_AVR_NANO_EVERY )
    static PinStatus ISREdge;	// Holder of the Next Edge we're looking for: RISING or FALLING
#elif defined ( ARDUINO_ARCH_RP2040) 
    static PinStatus ISREdge;	// Holder of the Next Edge we're looking for: RISING or FALLING
    static byte  ISRWatch;  // Interrupt Handler Edge Filter
#elif defined ( ARDUINO_ARCH_RENESAS_UNO) 
    static PinStatus ISREdge;	// Holder of the Next Edge we're looking for: RISING or FALLING
    static byte  ISRWatch;  // Interrupt Handler Edge Filter
#else
    static byte  ISREdge;   // Holder of the Next Edge we're looking for: RISING or FALLING
    static byte  ISRWatch;  // Interrupt Handler Edge Filter
#endif
byte ISRLevel;          // expected Level at DCC input during ISR ( to detect glitches )
byte ISRChkMask;       // Flag if Level must be checked
static word  bitMax, bitMin;

typedef enum
{
    WAIT_PREAMBLE = 0,
    WAIT_START_BIT,
    #ifndef SYNC_ALWAYS
    WAIT_START_BIT_FULL,
    #endif
    WAIT_DATA,
    WAIT_END_BIT
}
DccRxWaitState ;

typedef enum
{
    OPS_INS_RESERVED = 0,
    OPS_INS_VERIFY_BYTE,
    OPS_INS_BIT_MANIPULATION,
    OPS_INS_WRITE_BYTE
}
OpsInstructionType;

struct DccRx_t
{
    DccRxWaitState  State ;
    uint8_t         DataReady ;
    uint8_t         BitCount ;
    uint8_t         TempByte ;
    uint8_t         chkSum;
    DCC_MSG         PacketBuf;
    DCC_MSG         PacketCopy;
}
DccRx ;

typedef struct
{
    uint8_t   Flags ;
    uint8_t   OpsModeAddressBaseCV ;
    uint8_t   inServiceMode ;
    long      LastServiceModeMillis ;
    uint8_t   PageRegister ;  // Used for Paged Operations in Service Mode Programming
    uint8_t   DuplicateCount ;
    DCC_MSG   LastMsg ;
    uint8_t   ExtIntNum;
    uint8_t   ExtIntPinNum;
    volatile uint8_t   *ExtIntPort;     // use port and bitmask to read input at AVR in ISR
    uint8_t   ExtIntMask;     // digitalRead is too slow on AVR
    int16_t   myDccAddress;	// Cached value of DCC Address from CVs
    uint8_t   inAccDecDCCAddrNextReceivedMode;
    uint8_t   cv29Value;
    #ifdef DCC_DEBUG
    uint8_t   IntCount;
    uint8_t   TickCount;
    uint8_t   NestedIrqCount;
    #endif
}
DCC_PROCESSOR_STATE ;

DCC_PROCESSOR_STATE DccProcState ;

#ifdef ESP32
    portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;

    void IRAM_ATTR ExternalInterruptHandler (void)
#elif defined(ESP8266)
void IRAM_ATTR ExternalInterruptHandler(void)
#else
    void ExternalInterruptHandler (void)
#endif
{
    SET_TP3;

    #if defined(ESP32) || defined ( ARDUINO_ARCH_RP2040)
//   switch (ISRWatch)
//   {
//     case RISING: if (digitalRead(DccProcState.ExtIntPinNum)) break;
//     case FALLING: if (digitalRead(DccProcState.ExtIntPinNum)) return; break;
//   }
    // First compare the edge we're looking for to the pin state
    switch (ISRWatch)
    {
    case CHANGE:
        break;

    case RISING:
        if (digitalRead (DccProcState.ExtIntPinNum) != HIGH)
            return;
        break;

    case FALLING:
        if (digitalRead (DccProcState.ExtIntPinNum) != LOW)
            return;
        break;
    }
    #endif
// Bit evaluation without Timer 0 ------------------------------
    uint8_t DccBitVal;
    static int8_t  bit1, bit2 ;
    static unsigned int  lastMicros = 0;
    static byte halfBit, preambleBitCount;
    unsigned int  actMicros, bitMicros;
    #ifdef ALLOW_NESTED_IRQ
    static byte DCC_IrqRunning;
    if (DCC_IrqRunning)
    {
        // nested DCC IRQ - obviously there are glitches
        // ignore this interrupt and increment glitchcounter
        CLR_TP3;
        #ifdef DCC_DEBUG
        DccProcState.NestedIrqCount++;
        #endif
        SET_TP3;
        return; //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> abort IRQ
    }
    #endif
    actMicros = micros();
    bitMicros = actMicros-lastMicros;

    CLR_TP3;
    SET_TP3;
    #ifdef __AVR_MEGA__
    if (bitMicros < bitMin || (DccRx.State != WAIT_START_BIT && (*DccProcState.ExtIntPort & DccProcState.ExtIntMask) != (ISRLevel)))
    {
    #else
    if (bitMicros < bitMin || (DccRx.State != WAIT_START_BIT && digitalRead (DccProcState.ExtIntPinNum) != (ISRLevel)))
    {
    #endif
        // too short - my be false interrupt due to glitch or false protocol  or level does not match RISING / FALLING edge -> ignore this IRQ
        CLR_TP3;
        SET_TP4; /*delayMicroseconds(1); */ CLR_TP4;
        return; //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> abort IRQ
    }
    CLR_TP3;
    SET_TP3;

    lastMicros = actMicros;
    #ifndef SUPPORT_ZERO_BIT_STRETCHING
    //if ( bitMicros > MAX_ZEROBITFULL ) {
    if (bitMicros > (bitMax*2))
    {
        // too long - may be wrong protocol -> start over
        DccRx.State = WAIT_PREAMBLE ;
        DccRx.BitCount = 0 ;
        preambleBitCount = 0;
        // SET_TP2; CLR_TP2;
        bitMax = MAX_PRAEAMBEL;
        bitMin = MIN_ONEBITFULL;
        #if defined ( __STM32F1__ )
        detachInterrupt (DccProcState.ExtIntNum);
        #endif
        #if defined(ESP32) || defined ( ARDUINO_ARCH_RP2040)
        ISRWatch = ISREdge;
        #else
        attachInterrupt (DccProcState.ExtIntNum, ExternalInterruptHandler, ISREdge);
        #endif
        // enable level-checking
        ISRChkMask = DccProcState.ExtIntMask;
        ISRLevel = (ISREdge==RISING) ? DccProcState.ExtIntMask : 0 ;
        CLR_TP3;
        //CLR_TP3;
        return; //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> abort IRQ
    }
    CLR_TP3;
    SET_TP3;
    #endif

    DccBitVal = (bitMicros < bitMax);

    #ifdef ALLOW_NESTED_IRQ
    DCC_IrqRunning = true;
    interrupts();  // time critical is only the micros() command,so allow nested irq's
    #endif

    #ifdef DCC_DEBUG
    DccProcState.TickCount++;
    #endif
    switch (DccRx.State)
    {
    case WAIT_PREAMBLE:
        // We don't have to do anything special - looking for a preamble condition is done always
        SET_TP2;
        break;

        #ifndef SYNC_ALWAYS
    case WAIT_START_BIT_FULL:
        // wait for startbit without level checking
        if (!DccBitVal)
        {
            // we got the startbit
            CLR_TP2;
            CLR_TP1;
            DccRx.State = WAIT_DATA ;
            CLR_TP1;
            // initialize packet buffer
            DccRx.PacketBuf.Size = 0;
            /*for(uint8_t i = 0; i< MAX_DCC_MESSAGE_LEN; i++ )
                DccRx.PacketBuf.Data[i] = 0;*/
            DccRx.PacketBuf.PreambleBits = preambleBitCount;
            DccRx.BitCount = 0 ;
            DccRx.chkSum = 0 ;
            DccRx.TempByte = 0 ;
            //SET_TP1;
        }
        break;
        #endif
    case WAIT_START_BIT:
        // we are looking for first half "0" bit after preamble
        switch (halfBit)
        {
        case 0:
            // check first part
            if (DccBitVal)
            {
                // is still 1-bit (Preamble)
                halfBit=1;
                bit1=bitMicros;
            }
            else
            {
                // was "0" half bit, maybe the startbit
                halfBit = 4;
            }
            break;
        case 1: // previous halfbit was '1'
            if (DccBitVal)
            {
                // its a '1' halfBit -> we are still in the preamble
                halfBit = 0;
                bit2=bitMicros;
                preambleBitCount++;
                if (abs (bit2-bit1) > MAX_BITDIFF)
                {
                    // the length of the 2 halfbits differ too much -> wrong protokoll
                    DccRx.State = WAIT_PREAMBLE;
                    bitMax = MAX_PRAEAMBEL;
                    bitMin = MIN_ONEBITFULL;
                    preambleBitCount = 0;
                    // SET_TP2; CLR_TP2;
                    #if defined ( __STM32F1__ )
                    detachInterrupt (DccProcState.ExtIntNum);
                    #endif
                    #if defined(ESP32) || defined ( ARDUINO_ARCH_RP2040)
                    ISRWatch = ISREdge;
                    #else
                    attachInterrupt (DccProcState.ExtIntNum, ExternalInterruptHandler, ISREdge);
                    #endif
                    // enable level checking ( with direct port reading @ AVR )
                    ISRChkMask = DccProcState.ExtIntMask;
                    ISRLevel = (ISREdge==RISING) ? DccProcState.ExtIntMask : 0 ;
                    SET_TP3;
                    CLR_TP4;
                }
            }
            else
            {
                // first '0' half detected in second halfBit
                // wrong sync or not a DCC protokoll
                CLR_TP3;
                halfBit = 3;
                SET_TP3;
            }
            break;
        case 3: // previous halfbit was '0'  in second halfbit
            if (DccBitVal)
            {
                // its a '1' halfbit -> we got only a half '0' bit -> cannot be DCC
                DccRx.State = WAIT_PREAMBLE;
                bitMax = MAX_PRAEAMBEL;
                bitMin = MIN_ONEBITFULL;
                preambleBitCount = 0;
                // SET_TP2; CLR_TP2;
            }
            else
            {
                // we got two '0' halfbits -> it's the startbit
                // but sync is NOT ok, change IRQ edge.
                CLR_TP2;
                CLR_TP1;
                if (ISREdge == RISING) ISREdge = FALLING;
                else ISREdge = RISING;
                DccRx.State = WAIT_DATA ;
                CLR_TP1;
                bitMax = MAX_ONEBITFULL;
                bitMin = MIN_ONEBITFULL;
                DccRx.PacketBuf.Size = 0;
                /*for(uint8_t i = 0; i< MAX_DCC_MESSAGE_LEN; i++ )
                DccRx.PacketBuf.Data[i] = 0;*/
                DccRx.PacketBuf.PreambleBits = preambleBitCount;
                DccRx.BitCount = 0 ;
                DccRx.chkSum = 0 ;
                DccRx.TempByte = 0 ;
                //SET_TP1;
            }
            //SET_TP4;

            #if defined ( __STM32F1__ )
            detachInterrupt (DccProcState.ExtIntNum);
            #endif
            #if defined(ESP32) || defined ( ARDUINO_ARCH_RP2040)
            ISRWatch = ISREdge;
            #else
            attachInterrupt (DccProcState.ExtIntNum, ExternalInterruptHandler, ISREdge);
            #endif
            // enable level-checking
            ISRChkMask = DccProcState.ExtIntMask;
            ISRLevel = (ISREdge==RISING) ? DccProcState.ExtIntMask : 0 ;
            //CLR_TP4;
            break;
        case 4: // previous (first) halfbit was 0
            // if this halfbit is 0 too, we got the startbit
            if (DccBitVal)
            {
                // second halfbit is 1 -> unknown protokoll
                DccRx.State = WAIT_PREAMBLE;
                bitMax = MAX_PRAEAMBEL;
                bitMin = MIN_ONEBITFULL;
                preambleBitCount = 0;
                CLR_TP2;
                CLR_TP1;
                DccRx.BitCount = 0;
            }
            else
            {
                // we got the startbit
                CLR_TP2;
                CLR_TP1;
                DccRx.State = WAIT_DATA ;
                CLR_TP1;
                bitMax = MAX_ONEBITFULL;
                bitMin = MIN_ONEBITFULL;
                // initialize packet buffer
                DccRx.PacketBuf.Size = 0;
                /*for(uint8_t i = 0; i< MAX_DCC_MESSAGE_LEN; i++ )
                DccRx.PacketBuf.Data[i] = 0;*/
                DccRx.PacketBuf.PreambleBits = preambleBitCount;
                DccRx.BitCount = 0 ;
                DccRx.chkSum = 0 ;
                DccRx.TempByte = 0 ;
                //SET_TP1;
            }

            //SET_TP4;

            #if defined ( __STM32F1__ )
            detachInterrupt (DccProcState.ExtIntNum);
            #endif

            #if defined(ESP32) || defined ( ARDUINO_ARCH_RP2040)
            ISRWatch = ISREdge;
            #else
            attachInterrupt (DccProcState.ExtIntNum, ExternalInterruptHandler, ISREdge);
            #endif
            // enable level-checking
            ISRChkMask = DccProcState.ExtIntMask;
            ISRLevel = (ISREdge==RISING) ? DccProcState.ExtIntMask : 0 ;

            //CLR_TP4;
            break;

        }
        break;

    case WAIT_DATA:
        CLR_TP2;
        DccRx.BitCount++;
        DccRx.TempByte = (DccRx.TempByte << 1) ;
        if (DccBitVal)
            DccRx.TempByte |= 1 ;

        if (DccRx.BitCount == 8)
        {
            if (DccRx.PacketBuf.Size == MAX_DCC_MESSAGE_LEN)  // Packet is too long - abort
            {
                DccRx.State = WAIT_PREAMBLE ;
                bitMax = MAX_PRAEAMBEL;
                bitMin = MIN_ONEBITFULL;
                DccRx.BitCount = 0 ;
            }
            else
            {
                DccRx.State = WAIT_END_BIT ;
                DccRx.PacketBuf.Data[ DccRx.PacketBuf.Size++ ] = DccRx.TempByte ;
                DccRx.chkSum ^= DccRx.TempByte;
            }
        }
        break;

    case WAIT_END_BIT:
        SET_TP2;
        CLR_TP2;
        DccRx.BitCount++;
        if (DccBitVal)    // End of packet?
        {
            CLR_TP3;
            SET_TP4;
            DccRx.State = WAIT_PREAMBLE ;
            DccRx.BitCount = 0 ;
            bitMax = MAX_PRAEAMBEL;
            bitMin = MIN_ONEBITFULL;
            SET_TP1;
            if (DccRx.chkSum == 0)
            {
                // Packet is valid
                #ifdef ESP32
                portENTER_CRITICAL_ISR (&mux);
                #endif
                DccRx.PacketCopy = DccRx.PacketBuf ;
                DccRx.DataReady += 1 ;
                #ifdef ESP32
                portEXIT_CRITICAL_ISR (&mux);
                #endif
                // SET_TP2; CLR_TP2;
                preambleBitCount = 0 ;
            }
            else
            {
                // Wrong checksum
                CLR_TP1;
                #ifdef DCC_DBGVAR
                DB_PRINT ("Cerr");
                countOf.Err++;
                #endif
            }

            SET_TP3;
            CLR_TP4;
        }
        else      // Get next Byte
        {
            // KGW - Abort immediately if packet is too long.
            if (DccRx.PacketBuf.Size == MAX_DCC_MESSAGE_LEN)  // Packet is too long - abort
            {
                DccRx.State = WAIT_PREAMBLE ;
                bitMax = MAX_PRAEAMBEL;
                bitMin = MIN_ONEBITFULL;
                DccRx.BitCount = 0 ;
            }
            else
            {
                DccRx.State = WAIT_DATA ;

                DccRx.BitCount = 0 ;
                DccRx.TempByte = 0 ;
            }
        }
    }

    // unless we're already looking for the start bit
    // we always search for a preamble ( ( 10 or more consecutive 1 bits )
    // if we found it within a packet, the packet decoding is aborted because
    // that much one bits cannot be valid in a packet.
    if (DccRx.State != WAIT_START_BIT)
    {
        if (DccBitVal)
        {
            preambleBitCount++;
            //SET_TP2;
            if (preambleBitCount > 10)
            {
                CLR_TP2;
                #ifndef SYNC_ALWAYS
                if (DccRx.chkSum == 0)
                {
                    // sync must be correct if chksum was ok, no need to check sync
                    DccRx.State = WAIT_START_BIT_FULL;
                }
                else
                {
                #endif
                    DccRx.State = WAIT_START_BIT ;
                    SET_TP2;
                    // While waiting for the start bit, detect halfbit lengths. We will detect the correct
                    // sync and detect whether we see a false (e.g. motorola) protocol

                    #if defined ( __STM32F1__ )
                    detachInterrupt (DccProcState.ExtIntNum);
                    #endif
                    #if defined(ESP32) || defined ( ARDUINO_ARCH_RP2040)
                    ISRWatch = CHANGE;
                    #else
                    attachInterrupt (DccProcState.ExtIntNum, ExternalInterruptHandler, CHANGE);
                    #endif
                    ISRChkMask = 0;         // AVR level check is always true with this settings
                    ISRLevel = 0;           // ( there cannot be false edge IRQ's with CHANGE )
                    halfBit = 0;
                    bitMax = MAX_ONEBITHALF;
                    bitMin = MIN_ONEBITHALF;
                    //CLR_TP1;
                    #ifndef SYNC_ALWAYS
                }
                    #endif
            }
        }
        else
        {
            CLR_TP1;
            preambleBitCount = 0 ;
            // SET_TP2; CLR_TP2;
        }
    }

    #ifdef ALLOW_NESTED_IRQ
    DCC_IrqRunning = false;
    #endif
    //CLR_TP1;
    CLR_TP3;
}

void ackCV (void)
{
    if (notifyCVAck)
    {
        DB_PRINT ("ackCV: Send Basic ACK");
        notifyCVAck() ;
    }
}

void ackAdvancedCV (void)
{
    if (notifyAdvancedCVAck && (DccProcState.cv29Value & CV29_RAILCOM_ENABLE))
    {
        DB_PRINT ("ackAdvancedCV: Send RailCom ACK");
        notifyAdvancedCVAck() ;
    }
}


uint8_t readEEPROM (unsigned int CV)
{
    return EEPROM.read (CV) ;
}

void writeEEPROM (unsigned int CV, uint8_t Value)
{
    EEPROM.write (CV, Value) ;
    
	#if defined(ESP8266)
	noInterrupts();
	#endif

    #if defined(ESP8266) ||  defined(ESP32) || defined(ARDUINO_ARCH_RP2040)
    EEPROM.commit();
    #endif
    
	#if defined(ESP8266)
	interrupts();
	#endif
}

bool readyEEPROM()
{
    #if defined ARDUINO_ARCH_MEGAAVR
    return bit_is_clear (NVMCTRL.STATUS,NVMCTRL_EEBUSY_bp);
    #elif defined __AVR_MEGA__
    return eeprom_is_ready();
    #else
    return true;
    #endif
}

uint8_t validCV (uint16_t CV, uint8_t Writable)
{
    if (notifyCVResetFactoryDefault && (CV == CV_MANUFACTURER_ID)  && Writable)
        notifyCVResetFactoryDefault();

    if (notifyCVValid)
        return notifyCVValid (CV, Writable) ;

    uint8_t Valid = 1 ;

    if (CV > MAXCV)
        Valid = 0 ;

    if (Writable && ( (CV ==CV_VERSION_ID) || (CV == CV_MANUFACTURER_ID)))
        Valid = 0 ;

    return Valid ;
}

uint8_t readCV (unsigned int CV)
{
    uint8_t Value ;

    if (notifyCVRead)
        return notifyCVRead (CV) ;

    Value = readEEPROM (CV);
    return Value ;
}

uint8_t writeCV (unsigned int CV, uint8_t Value)
{
    switch (CV)
    {
    case CV_29_CONFIG:
        // copy addressmode Bit to Flags
        Value = Value &  ~CV29_RAILCOM_ENABLE;   // Bidi (RailCom) Bit must not be enabled,
        // because you cannot build a Bidi decoder with this lib.
        DccProcState.cv29Value = Value;
        DccProcState.Flags = (DccProcState.Flags & ~FLAGS_CV29_BITS) | (Value & FLAGS_CV29_BITS);
    // no break, because myDccAdress must also be reset
    case CV_ACCESSORY_DECODER_ADDRESS_LSB:	// Also same CV for CV_MULTIFUNCTION_PRIMARY_ADDRESS
    case CV_ACCESSORY_DECODER_ADDRESS_MSB:
    case CV_MULTIFUNCTION_EXTENDED_ADDRESS_MSB:
    case CV_MULTIFUNCTION_EXTENDED_ADDRESS_LSB:
        DccProcState.myDccAddress = -1;	// Assume any CV Write Operation might change the Address
    }

    if (notifyCVWrite)
        return notifyCVWrite (CV, Value) ;

    if (readEEPROM (CV) != Value)
    {
        writeEEPROM (CV, Value) ;

        if (notifyCVChange)
            notifyCVChange (CV, Value) ;

        if (notifyDccCVChange && ! (DccProcState.Flags & FLAGS_SETCV_CALLED))
            notifyDccCVChange (CV, Value);
    }

    return readEEPROM (CV) ;
}

uint16_t getMyAddr (void)
{
    if (DccProcState.myDccAddress != -1)	// See if we can return the cached value
        return (DccProcState.myDccAddress);

    if (DccProcState.cv29Value & CV29_ACCESSORY_DECODER)   // Accessory Decoder?
    {
        if (DccProcState.cv29Value & CV29_OUTPUT_ADDRESS_MODE)
            DccProcState.myDccAddress = (readCV (CV_ACCESSORY_DECODER_ADDRESS_MSB) << 8) | readCV (CV_ACCESSORY_DECODER_ADDRESS_LSB);
        else
            DccProcState.myDccAddress = ( (readCV (CV_ACCESSORY_DECODER_ADDRESS_MSB) & 0b00000111) << 6) | (readCV (CV_ACCESSORY_DECODER_ADDRESS_LSB) & 0b00111111) ;
    }
    else   // Multi-Function Decoder?
    {
        if (DccProcState.cv29Value & CV29_EXT_ADDRESSING)   // Two Byte Address?
            DccProcState.myDccAddress = ( (readCV (CV_MULTIFUNCTION_EXTENDED_ADDRESS_MSB) - 192) << 8) | readCV (CV_MULTIFUNCTION_EXTENDED_ADDRESS_LSB) ;

        else
            DccProcState.myDccAddress = readCV (1) ;
    }

    return DccProcState.myDccAddress ;
}

uint16_t getOpsAddr (void)
{
    if ( DccProcState.OpsModeAddressBaseCV == 0)
            return getMyAddr();
    
    // has OpsModeAddress
    uint16_t OpsAddr = readCV (DccProcState.OpsModeAddressBaseCV) | (readCV (DccProcState.OpsModeAddressBaseCV + 1) << 8) ;
    
    return OpsAddr ;
}

void processDirectCVOperation (uint8_t Cmd, uint16_t CVAddr, uint8_t Value, void (*ackFunction) ())
{
    // is it a Byte Operation
    if (Cmd & 0x04)
    {
        // Perform the Write Operation
        if (Cmd & 0x08)
        {
            if (validCV (CVAddr, 1))
            {
                DB_PRINT ("CV: %d Byte Write: %02X", CVAddr, Value)
                if (writeCV (CVAddr, Value) == Value)
                    ackFunction();
            }
        }

        else  // Perform the Verify Operation
        {
            if (validCV (CVAddr, 0))
            {
                DB_PRINT ("CV: %d Byte Read: %02X", CVAddr, Value)
                if (readCV (CVAddr) == Value)
                    ackFunction();
            }
        }
    }
    // Perform the Bit-Wise Operation
    else
    {
        uint8_t BitMask = (1 << (Value & 0x07)) ;
        uint8_t BitValue = Value & 0x08 ;
        uint8_t BitWrite = Value & 0x10 ;

        uint8_t tempValue = readCV (CVAddr) ;   // Read the Current CV Value

        DB_PRINT ("CV: %d Current Value: %02X  Bit-Wise Mode: %s  Mask: %02X  Value: %02X", CVAddr, tempValue, BitWrite ? "Write":"Read", BitMask, BitValue);

        // Perform the Bit Write Operation
        if (BitWrite)
        {
            if (validCV (CVAddr, 1))
            {
                if (BitValue)
                    tempValue |= BitMask ;     // Turn the Bit On

                else
                    tempValue &= ~BitMask ;  // Turn the Bit Off

                if (writeCV (CVAddr, tempValue) == tempValue)
                    ackFunction() ;
            }
        }

        // Perform the Bit Verify Operation
        else
        {
            if (validCV (CVAddr, 0))
            {
                if (BitValue)
                {
                    if (tempValue & BitMask)
                        ackFunction() ;
                }
                else
                {
                    if (! (tempValue & BitMask))
                        ackFunction() ;
                }
            }
        }
    }
}

/////////////////////////////////////////////////////////////////////////
#ifdef NMRA_DCC_PROCESS_MULTIFUNCTION
void processMultiFunctionMessage (uint16_t Addr, DCC_ADDR_TYPE AddrType, uint8_t Cmd, uint8_t Data1, uint8_t Data2)
{
    uint8_t  speed ;
    uint16_t CVAddr ;
    DCC_DIRECTION dir ;
    DCC_SPEED_STEPS speedSteps ;

    uint8_t  CmdMasked = Cmd & 0b11100000 ;

    // If we are an Accessory Decoder
    if (DccProcState.Flags & FLAGS_DCC_ACCESSORY_DECODER)
    {
        // and this isn't an Ops Mode Write or we are NOT faking the Multifunction Ops mode address in CV 33+34 or
        // it's not our fake address, then return
        if ( (CmdMasked != 0b11100000) || (DccProcState.OpsModeAddressBaseCV == 0))
            return ;

        uint16_t FakeOpsAddr = readCV (DccProcState.OpsModeAddressBaseCV) | (readCV (DccProcState.OpsModeAddressBaseCV + 1) << 8) ;
        uint16_t OpsAddr = Addr & 0x3FFF ;

        if (OpsAddr != FakeOpsAddr)
            return ;
    }

    // We are looking for FLAGS_MY_ADDRESS_ONLY but it does not match and it is not a Broadcast Address then return
    else if ( (DccProcState.Flags & FLAGS_MY_ADDRESS_ONLY) && (Addr != getMyAddr()) && (Addr != 0))
        return ;

    switch (CmdMasked)
    {
    case 0b00000000:  // Decoder Control
        switch (Cmd & 0b00001110)
        {
        case 0b00000000:
            if (notifyDccReset)
                notifyDccReset (Cmd & 0b00000001) ;
            break ;

        case 0b00000010:  // Factory Test
            break ;

        case 0b00000110:  // Set Decoder Flags
            break ;

        case 0b00001010:  // Set Advanced Addressing
            break ;

        case 0b00001110:  // Decoder Acknowledgment
            break ;

        default:    // Reserved
            ;
        }
        break ;

    case 0b00100000:  // Advanced Operations
        switch (Cmd & 0b00011111)
        {
        case 0b00011111:
            if (notifyDccSpeed)
            {
                switch (Data1 & 0b01111111)
                {
                case 0b00000000:  // 0=STOP
                    speed = 1 ;     // => 1
                    break ;

                case 0b00000001:  // 1=EMERGENCY_STOP
                    speed = 0 ;     // => 0
                    break ;

                default:          // 2..127
                    speed = (Data1 & 0b01111111) ;
                }
                dir = (DCC_DIRECTION) ( (Data1 & 0b10000000) >> 7) ;
                notifyDccSpeed (Addr, AddrType, speed, dir, SPEED_STEP_128) ;
            }
        }
        break;

    case 0b01000000:
    case 0b01100000:
        //TODO should we cache this info in DCC_PROCESSOR_STATE.Flags ?
        #ifdef NMRA_DCC_ENABLE_14_SPEED_STEP_MODE
        speedSteps = (DccProcState.cv29Value & CV29_F0_LOCATION) ? SPEED_STEP_28 : SPEED_STEP_14 ;
        #else
        speedSteps = SPEED_STEP_28 ;
        #endif
        if (notifyDccSpeed)
        {
            switch (Cmd & 0b00011111)
            {
            case 0b00000000:    // 0 0000 = STOP
            case 0b00010000:    // 1 0000 = STOP
                speed = 1 ;       // => 1
                break ;

            case 0b00000001:    // 0 0001 = EMERGENCY STOP
            case 0b00010001:    // 1 0001 = EMERGENCY STOP
                speed = 0 ;       // => 0
                break ;

            default:
                #ifdef NMRA_DCC_ENABLE_14_SPEED_STEP_MODE
                if (speedSteps == SPEED_STEP_14)
                {
                    speed = (Cmd & 0b00001111) ; // => 2..15
                }
                else
                {
                #endif
                    speed = ( ( (Cmd & 0b00001111) << 1) | ( (Cmd & 0b00010000) >> 4)) - 2 ; // => 2..29
                    #ifdef NMRA_DCC_ENABLE_14_SPEED_STEP_MODE
                }
                    #endif
            }
            dir = (DCC_DIRECTION) ( (Cmd & 0b00100000) >> 5) ;
            notifyDccSpeed (Addr, AddrType, speed, dir, speedSteps) ;
        }
        if (notifyDccSpeedRaw)
            notifyDccSpeedRaw (Addr, AddrType, Cmd);

        #ifdef NMRA_DCC_ENABLE_14_SPEED_STEP_MODE
        if (notifyDccFunc && (speedSteps == SPEED_STEP_14))
        {
            // function light is controlled by this package
            uint8_t fn0 = (Cmd & 0b00010000) ;
            notifyDccFunc (Addr, AddrType, FN_0, fn0) ;
        }
        #endif
        break;

    case 0b10000000:  // Function Group 0..4
        if (notifyDccFunc)
        {
            // function light is controlled by this package (28 or 128 speed steps)
            notifyDccFunc (Addr, AddrType, FN_0_4, Cmd & 0b00011111) ;
        }
        break;

    case 0b10100000:  // Function Group 5..8
        if (notifyDccFunc)
        {
            if (Cmd & 0b00010000)
                notifyDccFunc (Addr, AddrType, FN_5_8,  Cmd & 0b00001111) ;
            else
                notifyDccFunc (Addr, AddrType, FN_9_12, Cmd & 0b00001111) ;
        }
        break;

    case 0b11000000:  // Feature Expansion Instruction
        switch (Cmd & 0b00011111)
        {
        case 0b00011110:
            if (notifyDccFunc)
                notifyDccFunc (Addr, AddrType, FN_13_20, Data1) ;
            break;

        case 0b00011111:
            if (notifyDccFunc)
                notifyDccFunc (Addr, AddrType, FN_21_28, Data1) ;
            break;
        }
        break;

    case 0b11100000:  // CV Access
        CVAddr = ( ( (Cmd & 0x03) << 8) | Data1) + 1 ;

        processDirectCVOperation (Cmd, CVAddr, Data2, ackAdvancedCV) ;
        break;
    }
}
#endif

/////////////////////////////////////////////////////////////////////////
#ifdef NMRA_DCC_PROCESS_SERVICEMODE
void processServiceModeOperation (DCC_MSG * pDccMsg)
{
    uint16_t CVAddr ;
    uint8_t Value ;
    if (pDccMsg->Size == 3) // 3 Byte Packets are for Address Only, Register and Paged Mode
    {
        uint8_t RegisterAddr ;
        DB_PRINT ("CV Address, Register & Paged Mode Operation");
        RegisterAddr = pDccMsg->Data[0] & 0x07 ;
        Value = pDccMsg->Data[1] ;

        if (RegisterAddr == 5)
        {
            DccProcState.PageRegister = Value ;
            ackCV();
        }

        else
        {
            if (RegisterAddr == 4)
                CVAddr = CV_29_CONFIG ;

            else if ( (RegisterAddr <= 3) && (DccProcState.PageRegister > 0))
                CVAddr = ( (DccProcState.PageRegister - 1) * 4) + RegisterAddr + 1 ;

            else
                CVAddr = RegisterAddr + 1 ;

            if (pDccMsg->Data[0] & 0x08)  // Perform the Write Operation
            {
                if (validCV (CVAddr, 1))
                {
                    if (writeCV (CVAddr, Value) == Value)
                        ackCV();
                }
            }

            else  // Perform the Verify Operation
            {
                if (validCV (CVAddr, 0))
                {
                    if (readCV (CVAddr) == Value)
                        ackCV();
                }
            }
        }
    }

    else if (pDccMsg->Size == 4) // 4 Byte Packets are for Direct Byte & Bit Mode
    {
        DB_PRINT ("CV Direct Byte and Bit Mode Mode Operation");
        CVAddr = ( ( (pDccMsg->Data[0] & 0x03) << 8) | pDccMsg->Data[1]) + 1 ;
        Value = pDccMsg->Data[2] ;

        processDirectCVOperation (pDccMsg->Data[0] & 0b00001100, CVAddr, Value, ackCV) ;
    }
}
#endif

/////////////////////////////////////////////////////////////////////////
void resetServiceModeTimer (uint8_t inServiceMode)
{
    if (notifyServiceMode && inServiceMode != DccProcState.inServiceMode)
    {
        notifyServiceMode (inServiceMode);
    }
    // Set the Service Mode
    DccProcState.inServiceMode = inServiceMode ;

    DccProcState.LastServiceModeMillis = inServiceMode ? millis() : 0 ;
    if (notifyServiceMode && inServiceMode != DccProcState.inServiceMode)
    {
        notifyServiceMode (inServiceMode);
    }
}

/////////////////////////////////////////////////////////////////////////
void clearDccProcState (uint8_t inServiceMode)
{
    resetServiceModeTimer (inServiceMode) ;

    // Set the Page Register to it's default of 1 only on the first Reset
    DccProcState.PageRegister = 1 ;

    // Clear the LastMsg buffer and DuplicateCount in preparation for possible CV programming
    DccProcState.DuplicateCount = 0 ;
    memset (&DccProcState.LastMsg, 0, sizeof (DCC_MSG)) ;
}

/////////////////////////////////////////////////////////////////////////
#ifdef DEBUG_PRINT
void SerialPrintPacketHex (const __FlashStringHelper *strLabel, DCC_MSG * pDccMsg)
{
    Serial.print (strLabel);

    for (uint8_t i = 0; i < pDccMsg->Size; i++)
    {
        if (pDccMsg->Data[i] <= 9)
            Serial.print ('0');

        Serial.print (pDccMsg->Data[i], HEX);
        Serial.write (' ');
    }
    Serial.println();
}
#endif

///////////////////////////////////////////////////////////////////////////////
void execDccProcessor (DCC_MSG * pDccMsg)
{
    if ( (pDccMsg->Data[0] == 0) && (pDccMsg->Data[1] == 0))
    {
        if (notifyDccReset)
            notifyDccReset (0) ;

        #ifdef NMRA_DCC_PROCESS_SERVICEMODE
        // If this is the first Reset then perform some one-shot actions as we maybe about to enter service mode
        if (DccProcState.inServiceMode)
            resetServiceModeTimer (1) ;
        else
            clearDccProcState (1);
        #endif
    }

    else
    {
        #ifdef NMRA_DCC_PROCESS_SERVICEMODE
        if (DccProcState.inServiceMode && (pDccMsg->Data[0] >= 112) && (pDccMsg->Data[0] < 128))
        {
            resetServiceModeTimer (1) ;

            //Only check the DCC Packet "Size" and "Data" fields and ignore the "PreambleBits" as they can be different to the previous packet
            if (pDccMsg->Size != DccProcState.LastMsg.Size || memcmp (pDccMsg->Data, &DccProcState.LastMsg.Data, pDccMsg->Size) != 0)
            {
                DccProcState.DuplicateCount = 0 ;
                memcpy (&DccProcState.LastMsg, pDccMsg, sizeof (DCC_MSG)) ;
            }
            // Wait until you see 2 identical packets before acting on a Service Mode Packet
            else
            {
                DccProcState.DuplicateCount++ ;
                processServiceModeOperation (pDccMsg) ;
            }
        }

        else
        {
            if (DccProcState.inServiceMode)
                clearDccProcState (0);
        #endif

            // Idle Packet
            if ( (pDccMsg->Data[0] == 0b11111111) && (pDccMsg->Data[1] == 0))
            {
                if (notifyDccIdle)
                    notifyDccIdle() ;
            }


    	    // RCN-211 Fastclock
	        // Address=0 (broadcast) Cmd=110-00001
	        else if (pDccMsg->Data[0] == 0b00000000 && pDccMsg->Data[1] == 0b11000001) {

                uint8_t cc = pDccMsg->Data[2] & 0b11000000;

                if ( cc == 0b00000000) {
                    uint8_t fastClockMinutes = pDccMsg->Data[2] & 0b00111111;
                    uint8_t fastClockHours   = pDccMsg->Data[3] & 0b00011111;
                    uint8_t fastClockWeekday = (pDccMsg->Data[3] & 0b11100000) >> 5;
                    uint8_t fastClockFactor  = pDccMsg->Data[4] & 0b00111111;
                    bool fastClockUpdate  = (pDccMsg->Data[4] & 0b10000000) > 0;

                    if ( notifyFastClockTime) {
                        notifyFastClockTime( fastClockWeekday, fastClockHours, fastClockMinutes, fastClockFactor, fastClockUpdate);
                    }
                }
                else if ( cc == 0b01000000) {
                    uint8_t fastClockDayOfMonth = pDccMsg->Data[2] & 0b00011111;
                    uint8_t  fastClockMonth      = (pDccMsg->Data[3] & 0b11110000) >> 4;
                    uint16_t fastClockYear       = (uint16_t)((pDccMsg->Data[3] & 0b00001111) << 12) + pDccMsg->Data[4];

                    if ( notifyFastClockDate) {
                        notifyFastClockDate( fastClockDayOfMonth, fastClockMonth, fastClockYear);
                    }
                }
                else if ( cc == 0b10000000) {
                    // Factor as float. Not supported yet.
                }
		    }
    	    // RCN-211 System time
	        // Address=0 (broadcast) Cmd=110-00010
            else if (pDccMsg->Data[0] == 0b00000000 && pDccMsg->Data[1] == 0b11000010) {
                uint16_t millisSinceSystemStart = ((uint16_t)pDccMsg->Data[2] << 8) + pDccMsg->Data[3];

                if ( notifySystemTime) {
                    notifySystemTime( millisSinceSystemStart);
                }
            }


            #ifdef NMRA_DCC_PROCESS_MULTIFUNCTION
            // Multi Function Decoders (7-bit address)
            else if (pDccMsg->Data[0] < 128)
                processMultiFunctionMessage (pDccMsg->Data[0], DCC_ADDR_SHORT, pDccMsg->Data[1], pDccMsg->Data[2], pDccMsg->Data[3]) ;

            // Basic Accessory Decoders (9-bit) & Extended Accessory Decoders (11-bit)
            else if (pDccMsg->Data[0] < 192)
            #else
            else if ( (pDccMsg->Data[0] >= 128) && (pDccMsg->Data[0] < 192))
            #endif
            {
                if (DccProcState.Flags & FLAGS_DCC_ACCESSORY_DECODER)
                {
                    int16_t BoardAddress ;
                    int16_t OutputAddress ;
                    uint8_t TurnoutPairIndex ;

                    #ifdef DEBUG_PRINT
                    SerialPrintPacketHex (F ("eDP: AccCmd: "), pDccMsg);
                    #endif

                    BoardAddress = ( ( (~pDccMsg->Data[1]) & 0b01110000) << 2) | (pDccMsg->Data[0] & 0b00111111) ;
                    TurnoutPairIndex = (pDccMsg->Data[1] & 0b00000110) >> 1;
                    DB_PRINT ("eDP: BAddr:%d, Index:%d", BoardAddress, TurnoutPairIndex);

                    // First check for Legacy Accessory Decoder Configuration Variable Access Instruction
                    // as it's got a different format to the others
                    if ( (pDccMsg->Size == 5) && ( (pDccMsg->Data[1] & 0b10001100) == 0b00001100))
                    {
                        DB_PRINT ("eDP: Legacy Accessory Decoder CV Access Command");
                        // Check if this command is for our address or the broadcast address
                        if ( (BoardAddress != getMyAddr()) && (BoardAddress < 511))
                        {
                            DB_PRINT ("eDP: Board Address Not Matched");
                            return;
                        }

                        uint16_t cvAddress = ( (pDccMsg->Data[1] & 0b00000011) << 8) + pDccMsg->Data[2] + 1;
                        uint8_t  cvValue   = pDccMsg->Data[3];
                        DB_PRINT ("eDP: CV:%d Value:%d", cvAddress, cvValue);
                        if (validCV (cvAddress, 1))
                            writeCV (cvAddress, cvValue);
                        return;
                    }


                    OutputAddress = ( ( (BoardAddress - 1) << 2) | TurnoutPairIndex) + 1 ; //decoder output addresses start with 1, packet address range starts with 0
                    // ( according to NMRA 9.2.2 )
                    DB_PRINT ("eDP: OAddr:%d", OutputAddress);

                    if (DccProcState.inAccDecDCCAddrNextReceivedMode)
                    {
                        if (DccProcState.Flags & FLAGS_OUTPUT_ADDRESS_MODE)
                        {
                            DB_PRINT ("eDP: Set OAddr:%d", OutputAddress);
                            //uint16_t storedOutputAddress = OutputAddress + 1; // The value stored in CV1 & 9 for Output Addressing Mode is + 1
                            writeCV (CV_ACCESSORY_DECODER_ADDRESS_LSB, (uint8_t) (OutputAddress % 256));
                            writeCV (CV_ACCESSORY_DECODER_ADDRESS_MSB, (uint8_t) (OutputAddress / 256));

                            if (notifyDccAccOutputAddrSet)
                                notifyDccAccOutputAddrSet (OutputAddress);
                        }
                        else
                        {
                            DB_PRINT ("eDP: Set BAddr:%d", BoardAddress);
                            writeCV (CV_ACCESSORY_DECODER_ADDRESS_LSB, (uint8_t) (BoardAddress % 64));
                            writeCV (CV_ACCESSORY_DECODER_ADDRESS_MSB, (uint8_t) (BoardAddress / 64));

                            if (notifyDccAccBoardAddrSet)
                                notifyDccAccBoardAddrSet (BoardAddress);
                        }

                        DccProcState.inAccDecDCCAddrNextReceivedMode = 0; // Reset the mode now that we have set the address
                    }

                    // If we're filtering addresses, does the address match our address or is it a broadcast address? If NOT then return
                    if (DccProcState.Flags & FLAGS_MY_ADDRESS_ONLY)
                    {
                        if (DccProcState.Flags & FLAGS_OUTPUT_ADDRESS_MODE)
                        {
                            DB_PRINT (" AddrChk: OAddr:%d, BAddr:%d, myAddr:%d Chk=%d", OutputAddress, BoardAddress, getMyAddr(), OutputAddress != getMyAddr());
                            if (OutputAddress != getMyAddr()  &&  OutputAddress < 2045)
                            {
                                DB_PRINT (" eDP: OAddr:%d, myAddr:%d - no match", OutputAddress, getMyAddr());
                                return;
                            }
                        }
                        else
                        {
                            if ( (BoardAddress != getMyAddr()) && (BoardAddress < 511))
                            {
                                DB_PRINT (" eDP: BAddr:%d, myAddr:%d - no match", BoardAddress, getMyAddr());
                                return;
                            }
                        }
                        DB_PRINT ("eDP: Address Matched");
                    }


                    if ( (pDccMsg->Size == 4) && ( (pDccMsg->Data[1] & 0b10001001) == 1))	// Extended Accessory Decoder Control Packet Format
                    {
                        // According to the NMRA Dcc Spec the Signal State should only use the lower 5 Bits,
                        // however some manufacturers seem to allow/use all 8 bits, so we'll relax that constraint for now
                        uint8_t state = pDccMsg->Data[2] ;
                        DB_PRINT ("eDP: OAddr:%d  Extended State:%0X", OutputAddress, state);
                        if (notifyDccSigOutputState)
                            notifyDccSigOutputState (OutputAddress, state);

                        // old callback ( for compatibility with 1.4.2, not to be used in new designs )
                        if (notifyDccSigState)
                            notifyDccSigState (OutputAddress, TurnoutPairIndex, pDccMsg->Data[2]) ;
                    }

                    else if (pDccMsg->Size == 3) // Basic Accessory Decoder Packet Format
                    {
                        uint8_t direction   =  pDccMsg->Data[1] & 0b00000001;
                        uint8_t outputPower = (pDccMsg->Data[1] & 0b00001000) >> 3;

                        // old callback ( for compatibility with 1.4.2, not to be used in new designs )
                        if (notifyDccAccState)
                            notifyDccAccState (OutputAddress, BoardAddress, pDccMsg->Data[1] & 0b00000111, outputPower);

                        if (DccProcState.Flags & FLAGS_OUTPUT_ADDRESS_MODE)
                        {
                            DB_PRINT ("eDP: OAddr:%d  Turnout Dir:%d  Output Power:%d", OutputAddress, direction, outputPower);
                            if (notifyDccAccTurnoutOutput)
                                notifyDccAccTurnoutOutput (OutputAddress, direction, outputPower);
                        }
                        else
                        {
                            DB_PRINT ("eDP: Turnout Pair Index:%d Dir:%d Output Power: ", TurnoutPairIndex, direction, outputPower);
                            if (notifyDccAccTurnoutBoard)
                                notifyDccAccTurnoutBoard (BoardAddress, TurnoutPairIndex, direction, outputPower);
                        }
                    }
                    else if (pDccMsg->Size == 6) // Accessory Decoder OPS Mode Programming
                    {
                        DB_PRINT ("eDP: OPS Mode CV Programming Command");
                        // Check for unsupported OPS Mode Addressing mode
                        if ( ( (pDccMsg->Data[1] & 0b10001001) != 1) && ( (pDccMsg->Data[1] & 0b10001111) != 0x80))
                        {
                            DB_PRINT ("eDP: Unsupported OPS Mode CV Addressing Mode");
                            return;
                        }

                        // Check if this command is for our address or the broadcast address
                        if (DccProcState.Flags & FLAGS_OUTPUT_ADDRESS_MODE)
                        {
                            DB_PRINT ("eDP: Check Output Address:%d", OutputAddress);
                            if ( (OutputAddress != getOpsAddr()) && (OutputAddress < 2045))
                            {
                                DB_PRINT ("eDP: Output Address Not Matched");
                                return;
                            }
                        }
                        else
                        {
                            DB_PRINT ("eDP: Check Board Address:%d", BoardAddress);
                            if ( (BoardAddress != getOpsAddr()) && (BoardAddress < 511))
                            {
                                DB_PRINT ("eDP: Board Address Not Matched");
                                return;
                            }
                        }

                        uint16_t cvAddress = ( (pDccMsg->Data[2] & 0b00000011) << 8) + pDccMsg->Data[3] + 1;
                        uint8_t  cvValue   = pDccMsg->Data[4];

                        OpsInstructionType insType = (OpsInstructionType) ( (pDccMsg->Data[2] & 0b00001100) >> 2) ;

                        DB_PRINT ("eDP: OPS Mode Instruction:%d", insType);
                        switch (insType)
                        {
                        case OPS_INS_RESERVED:
                        case OPS_INS_VERIFY_BYTE:
                            DB_PRINT ("eDP: Unsupported OPS Mode Instruction:%d", insType);
                            break; // We only support Write Byte or Bit Manipulation

                        case OPS_INS_WRITE_BYTE:
                            DB_PRINT ("eDP: CV:%d Value:%d", cvAddress, cvValue);
                            if (validCV (cvAddress, 1))
                                writeCV (cvAddress, cvValue);
                            break;

                        // 111CDBBB
                        // Where BBB represents the bit position within the CV,
                        // D contains the value of the bit to be verified or written,
                        // and C describes whether the operation is a verify bit or a write bit operation.
                        // C = "1" WRITE BIT
                        // C = "0" VERIFY BIT
                        case OPS_INS_BIT_MANIPULATION:
                            // Make sure its a Write Bit Manipulation
                            if ( (cvValue & 0b00010000) && validCV (cvAddress, 1))
                            {
                                uint8_t currentValue = readCV (cvAddress);
                                uint8_t newValueMask = 1 << (cvValue & 0b00000111);
                                if (cvValue & 0b00001000)
                                    writeCV (cvAddress, currentValue | newValueMask);
                                else
                                    writeCV (cvAddress, currentValue & ~newValueMask);
                            }
                            break;
                        }
                    }
                }
            }

            #ifdef NMRA_DCC_PROCESS_MULTIFUNCTION
            // Multi Function Decoders (14-bit address)
            else if (pDccMsg->Data[0] < 232)
            {
                uint16_t Address ;
                Address = ( (pDccMsg->Data[0] - 192) << 8) | pDccMsg->Data[1];
                //TODO should we convert Address to 1 .. 10239 ?
                processMultiFunctionMessage (Address, DCC_ADDR_LONG, pDccMsg->Data[2], pDccMsg->Data[3], pDccMsg->Data[4]) ;
            }
            #endif
            #ifdef NMRA_DCC_PROCESS_SERVICEMODE
        }
            #endif
    }
}

////////////////////////////////////////////////////////////////////////
NmraDcc::NmraDcc()
{
}

#ifdef digitalPinToInterrupt
void NmraDcc::pin (uint8_t ExtIntPinNum, uint8_t EnablePullup)
{
    pin (digitalPinToInterrupt (ExtIntPinNum), ExtIntPinNum, EnablePullup);
}
#endif

void NmraDcc::pin (uint8_t ExtIntNum, uint8_t ExtIntPinNum, uint8_t EnablePullup)
{
    #if defined ( __STM32F1__ )
    // with STM32F1 the interuptnumber is equal the pin number
    DccProcState.ExtIntNum = ExtIntPinNum;
    // because STM32F1 has a NVIC we must set interuptpriorities
    const nvic_irq_num irqNum2nvic[] = { NVIC_EXTI0, NVIC_EXTI1, NVIC_EXTI2, NVIC_EXTI3, NVIC_EXTI4,
                                         NVIC_EXTI_9_5, NVIC_EXTI_9_5, NVIC_EXTI_9_5, NVIC_EXTI_9_5, NVIC_EXTI_9_5,
                                         NVIC_EXTI_15_10, NVIC_EXTI_15_10, NVIC_EXTI_15_10, NVIC_EXTI_15_10, NVIC_EXTI_15_10, NVIC_EXTI_15_10
                                       };
    exti_num irqNum = (exti_num) (PIN_MAP[ExtIntPinNum].gpio_bit);

// DCC-Input IRQ must be able to interrupt other long low priority ( level15 ) IRQ's
    nvic_irq_set_priority (irqNum2nvic[irqNum], PRIO_DCC_IRQ);

// Systic must be able to interrupt DCC-IRQ to always get correct micros() values
    nvic_irq_set_priority (NVIC_SYSTICK, PRIO_SYSTIC);
    #else
    DccProcState.ExtIntNum = ExtIntNum;
    #endif
    DccProcState.ExtIntPinNum = ExtIntPinNum;
    #ifdef __AVR_MEGA__
    // because digitalRead at AVR is slow, we will read the dcc input in the ISR
    // by direct port access.
    DccProcState.ExtIntPort = portInputRegister (digitalPinToPort (ExtIntPinNum));
    DccProcState.ExtIntMask = digitalPinToBitMask (ExtIntPinNum);
    #else
    DccProcState.ExtIntMask = 1;
    #endif
    pinMode (ExtIntPinNum, EnablePullup ? INPUT_PULLUP : INPUT);
}

////////////////////////////////////////////////////////////////////////
void NmraDcc::initAccessoryDecoder (uint8_t ManufacturerId, uint8_t VersionId, uint8_t Flags, uint8_t OpsModeAddressBaseCV)
{
    init (ManufacturerId, VersionId, Flags | FLAGS_DCC_ACCESSORY_DECODER, OpsModeAddressBaseCV);
}

////////////////////////////////////////////////////////////////////////
void NmraDcc::init (uint8_t ManufacturerId, uint8_t VersionId, uint8_t Flags, uint8_t OpsModeAddressBaseCV)
{
    #if defined(ESP8266) ||  defined(ESP32) || defined(ARDUINO_ARCH_RP2040)
    EEPROM.begin (MAXCV);
    #endif
    // Clear all the static member variables
    memset (&DccRx, 0, sizeof (DccRx));

    MODE_TP1; // only for debugging and timing measurement
    MODE_TP2;
    MODE_TP3;
    MODE_TP4;
    bitMax = MAX_ONEBITFULL;
    bitMin = MIN_ONEBITFULL;

    DccProcState.Flags = Flags ;
    DccProcState.OpsModeAddressBaseCV = OpsModeAddressBaseCV ;
    DccProcState.myDccAddress = -1;
    DccProcState.inAccDecDCCAddrNextReceivedMode = 0;

    ISREdge = RISING;
    // level checking to detect false IRQ's fired by glitches
    ISRLevel = DccProcState.ExtIntMask;
    ISRChkMask = DccProcState.ExtIntMask;

    #if defined(ESP32)|| defined ( ARDUINO_ARCH_RP2040)
    ISRWatch = ISREdge;
    attachInterrupt (DccProcState.ExtIntNum, ExternalInterruptHandler, CHANGE);
    #else
    attachInterrupt (DccProcState.ExtIntNum, ExternalInterruptHandler, RISING);
    #endif

    // Set the Bits that control Multifunction or Accessory behaviour
    // and if the Accessory decoder optionally handles Output Addressing
    // we need to peal off the top two bits
    DccProcState.cv29Value = writeCV (CV_29_CONFIG, (readCV (CV_29_CONFIG) & ~FLAGS_CV29_BITS) | (Flags & FLAGS_CV29_BITS)) ;

    uint8_t doAutoFactoryDefault = 0;
    if ( (Flags & FLAGS_AUTO_FACTORY_DEFAULT) && (readCV (CV_VERSION_ID) == 255) && (readCV (CV_MANUFACTURER_ID) == 255))
        doAutoFactoryDefault = 1;

    writeCV (CV_VERSION_ID, VersionId) ;
    writeCV (CV_MANUFACTURER_ID, ManufacturerId) ;

    clearDccProcState (0);

    if (notifyCVResetFactoryDefault && doAutoFactoryDefault)
        notifyCVResetFactoryDefault();
}

////////////////////////////////////////////////////////////////////////
uint8_t NmraDcc::getCV (uint16_t CV)
{
    return readCV (CV);
}

////////////////////////////////////////////////////////////////////////
uint8_t NmraDcc::setCV (uint16_t CV, uint8_t Value)
{
    DccProcState.Flags |= FLAGS_SETCV_CALLED;

    uint8_t returnValue = writeCV (CV,Value);

    DccProcState.Flags &= ~FLAGS_SETCV_CALLED;

    return returnValue;
}

////////////////////////////////////////////////////////////////////////
uint16_t NmraDcc::getAddr (void)
{
    return getMyAddr();
}

////////////////////////////////////////////////////////////////////////
uint8_t NmraDcc::isSetCVReady (void)
{
    if (notifyIsSetCVReady)
        return notifyIsSetCVReady();
    return readyEEPROM();
}

////////////////////////////////////////////////////////////////////////
#ifdef DCC_DEBUG
uint8_t NmraDcc::getIntCount (void)
{
    return DccProcState.IntCount;
}

////////////////////////////////////////////////////////////////////////
uint8_t NmraDcc::getTickCount (void)
{
    return DccProcState.TickCount;
}

////////////////////////////////////////////////////////////////////////
uint8_t NmraDcc::getNestedIrqCount (void)
{
    return DccProcState.NestedIrqCount;
}

////////////////////////////////////////////////////////////////////////
uint8_t NmraDcc::getState (void)
{
    return DccRx.State;
}

////////////////////////////////////////////////////////////////////////
uint8_t NmraDcc::getBitCount (void)
{
    return DccRx.BitCount;
}
#endif

////////////////////////////////////////////////////////////////////////
void NmraDcc::setAccDecDCCAddrNextReceived (uint8_t enable)
{
    DccProcState.inAccDecDCCAddrNextReceivedMode = enable;
}

////////////////////////////////////////////////////////////////////////
uint8_t NmraDcc::process()
{
	uint8_t	copyDataReady ;
	
    if (DccProcState.inServiceMode)
    {
        if ( (millis() - DccProcState.LastServiceModeMillis) > 20L)
        {
            clearDccProcState (0) ;
        }
    }

    if (DccRx.DataReady)
    {
        // We need to do this check with interrupts disabled
        #ifdef ESP32
        portENTER_CRITICAL (&mux);
        #else
        noInterrupts();
        #endif
        Msg = DccRx.PacketCopy ;
        copyDataReady = DccRx.DataReady;
        DccRx.DataReady = 0 ;

        #ifdef ESP32
        portEXIT_CRITICAL (&mux);
        #else
        interrupts();
        #endif
        // Checking of the XOR-byte is now done in the ISR already
        #ifdef DCC_DBGVAR
        countOf.Tel++;
        #endif
        // Clear trailing bytes
        for (byte i=Msg.Size; i< MAX_DCC_MESSAGE_LEN; i++) Msg.Data[i] = 0;

        if (notifyDccMsg) 	notifyDccMsg (&Msg);

        execDccProcessor (&Msg);
        return copyDataReady ;
    }

    return 0 ;
};
